package com.franklin.ideaplugin.easytesting.controllerclient;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.easytesting.controllerclient.beans.MethodData;
import com.franklin.ideaplugin.easytesting.controllerclient.utils.MvcAnnotationUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;

/**
 * @author Ye Junhui
 * @since 2023/6/28
 */
@Slf4j
public class MethodBuilder implements EnvironmentAware {

    private final Map<Class,IMethodParameterProcessor> processorMap = new HashMap<>();
    private Environment environment;

    private static final String ACCEPT = "Accept";

    private static final String CONTENT_TYPE = "Content-Type";

    public MethodBuilder(List<IMethodParameterProcessor> methodParameterProcessors) {
        methodParameterProcessors.forEach(methodParameterProcessor -> processorMap.put(methodParameterProcessor.getAnnotationType(),methodParameterProcessor));
    }

    /**
     * 构建所有方法元数据
     * @param clazz
     * @return
     */
    public List<MethodData> buildAllMethodData(Class<?> clazz){
        //解析controller路径
        RequestMapping classAnnotation = findMergedAnnotation(clazz, RequestMapping.class);
        String baseUrl = MvcAnnotationUtils.getFirstPath(classAnnotation);
        baseUrl = resolve(baseUrl);

        Method[] declaredMethods = clazz.getDeclaredMethods();
        String finalBaseUrl = baseUrl;
        return Arrays.stream(declaredMethods)
                .map(method -> buildMethodData(finalBaseUrl,method))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    /**
     * 构建方法元数据
     * @param method
     * @return
     */
    protected MethodData buildMethodData(String baseUrl,Method method){
        //requestMapping注解
        RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        if (Objects.isNull(requestMapping)){
            return null;
        }

        MethodData methodData = new MethodData(method);

        //请求方法
        parseHttpMethod(methodData,requestMapping);

        //请求路径
        parseUrl(baseUrl,methodData,requestMapping);

        //参数
        parseArgument(methodData,method);

        //produces
        parseProduces(methodData,requestMapping);

        //consumes
        parseConsumes(methodData,requestMapping);

        //请求头
        parseHeaders(methodData,requestMapping);

        return methodData;
    }

    protected void parseHttpMethod(MethodData methodData, RequestMapping requestMapping) {

        RequestMethod[] requestMethods = requestMapping.method();
        if (ArrayUtil.isNotEmpty(requestMethods)){
            RequestMethod requestMethod = requestMethods[0];
            methodData.setHttpMethod(HttpMethod.valueOf(requestMethod.name()));
        }else {
            methodData.setHttpMethod(HttpMethod.POST);
        }

    }


    /**
     * 解析produce
     * @param methodData
     * @param requestMapping
     */
    protected void parseProduces(MethodData methodData, RequestMapping requestMapping) {
        String[] serverProduces = requestMapping.produces();
        String clientAccepts = serverProduces.length == 0 ? null
                : StrUtil.emptyToNull(serverProduces[0]);
        if (Objects.nonNull(clientAccepts)){
            methodData.getMethodHeaders().add(ACCEPT, clientAccepts);
        }
    }

    /**
     * 解析consumes
     * @param methodData
     * @param requestMapping
     */
    protected void parseConsumes(MethodData methodData, RequestMapping requestMapping) {
        String[] serverConsumes = requestMapping.consumes();
        String clientProduces = serverConsumes.length == 0 ? null
                : StrUtil.emptyToNull(serverConsumes[0]);
        if (Objects.nonNull(clientProduces)) {
            methodData.getMethodHeaders().add(CONTENT_TYPE, clientProduces);
        }
    }

    /**
     * 解析header
     * @param methodData
     * @param requestMapping
     */
    protected void parseHeaders(MethodData methodData,RequestMapping requestMapping){
        if (ArrayUtil.isNotEmpty(requestMapping.headers())){
            for (String header : requestMapping.headers()) {
                int index = header.indexOf('=');
                if (!header.contains("!=") && index >= 0) {
                    methodData.getMethodHeaders().add(
                            resolve(header.substring(0, index)),
                            resolve(header.substring(index + 1).trim())
                    );
                }
            }
        }
    }

    /**
     * 解析url
     * @param baseUrl
     * @param methodData
     * @param requestMapping
     */
    protected void parseUrl(String baseUrl,MethodData methodData,RequestMapping requestMapping){
        String url = "";
        if (ArrayUtil.isNotEmpty(requestMapping.value())){
            url = requestMapping.value()[0];
        }else if (ArrayUtil.isNotEmpty(requestMapping.path())){
            url = requestMapping.path()[0];
        }
        url = resolve(url);
        if (baseUrl.endsWith("/")){
            baseUrl = baseUrl.substring(0,baseUrl.length() - 1);
        }
        if (url.startsWith("/")){
            url = url.substring(1);
        }
        url = baseUrl + "/" + url;
        if (!url.startsWith("/")){
            url = "/" + url;
        }
        methodData.setUrl(url);
    }

    /**
     * 解析请求参数
     * @param methodData
     * @param method
     */
    protected void parseArgument(MethodData methodData,Method method){
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Class<?>[] genericParameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] annotations = parameterAnnotations[i];
            Class<?> parameterType = genericParameterTypes[i];
            if (ArrayUtil.isEmpty(annotations)){
                IMethodParameterProcessor methodParameterProcessor = processorMap.get(null);
                methodParameterProcessor.processParameter(methodData,null,i,parameterType);
            }else {
                boolean processStatus = false;
                for (Annotation annotation : annotations) {
                    IMethodParameterProcessor methodParameterProcessor = processorMap.get(annotation.annotationType());
                    if (Objects.isNull(methodParameterProcessor)){
                        continue;
                    }
                    processStatus = methodParameterProcessor.processParameter(methodData,annotation,i,parameterType);
                    break;
                }
                if (!processStatus){
                    //降级为，普通参数
                    IMethodParameterProcessor methodParameterProcessor = processorMap.get(null);
                    methodParameterProcessor.processParameter(methodData,null,i,parameterType);
                }
            }
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * 解析
     * @param value
     * @return
     */
    private String resolve(String value){
        if (StrUtil.isNotBlank(value)){
            return this.environment.resolvePlaceholders(value);
        }
        return value;
    }
}
